﻿using System;
using System.Reflection.Emit;
using Sigil.Impl;

namespace Sigil
{
	public partial class Emit<DelegateType>
	{
		/// <summary>
		/// Pops a value of the given type and a pointer off the stack, and stores the value at the address in the pointer.
		/// </summary>
		public Emit<DelegateType> StoreIndirect<Type>(Boolean isVolatile = false, Int32? unaligned = null)
		{
			return StoreIndirect(typeof(Type), isVolatile, unaligned);
		}

		/// <summary>
		/// Pops a value of the given type and a pointer off the stack, and stores the value at the address in the pointer.
		/// </summary>
		public Emit<DelegateType> StoreIndirect(Type type, Boolean isVolatile = false, Int32? unaligned = null)
		{
			if (type == null)
			{
				throw new ArgumentNullException("type");
			}

			if (unaligned.HasValue && (unaligned != 1 && unaligned != 2 && unaligned != 4))
			{
				throw new ArgumentException("unaligned must be null, 1, 2, or 4");
			}

			if (isVolatile)
			{
				UpdateState(OpCodes.Volatile, Wrap(StackTransition.None(), "StoreIndirect"));
			}

			if (unaligned.HasValue)
			{
				UpdateState(OpCodes.Unaligned, (Byte)unaligned.Value, Wrap(StackTransition.None(), "StoreIndirect"));
			}

			if (type.IsPointer)
			{
				var transition = new[] { new StackTransition(new[] { typeof(NativeIntType), typeof(NativeIntType) }, Type.EmptyTypes) };

				UpdateState(OpCodes.Stind_I, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (!type.IsValueType)
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { type, type.MakePointerType() }, Type.EmptyTypes),
                        new StackTransition(new[] { type, type.MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { type, typeof(NativeIntType) }, Type.EmptyTypes),
                    };

				UpdateState(OpCodes.Stind_Ref, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (type == typeof(sbyte) || type == typeof(Byte))
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { typeof(Int32), typeof(Byte*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(Byte).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(sbyte*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(sbyte).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(NativeIntType) }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stind_I1, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (type == typeof(Int16) || type == typeof(UInt16))
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { typeof(Int32), typeof(Int16*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(Int16).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(UInt16*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(UInt16).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(NativeIntType) }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stind_I2, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (type == typeof(Int32) || type == typeof(UInt32))
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { typeof(Int32), typeof(Int32*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(Int32).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(UInt32*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(UInt32).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int32), typeof(NativeIntType) }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stind_I4, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (type == typeof(Int64) || type == typeof(UInt64))
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { typeof(Int64), typeof(Int64*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int64), typeof(Int64).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int64), typeof(UInt64*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int64), typeof(UInt64).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Int64), typeof(NativeIntType) }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stind_I8, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (type == typeof(Single))
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { typeof(Single), typeof(Single*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Single), typeof(Single).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Single), typeof(NativeIntType) }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stind_R4, Wrap(transition, "StoreIndirect"));
				return this;
			}

			if (type == typeof(Double))
			{
				var transition =
						new[]
                    {
                        new StackTransition(new[] { typeof(Double), typeof(Double*) }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Double), typeof(Double).MakeByRefType() }, Type.EmptyTypes),
                        new StackTransition(new[] { typeof(Double), typeof(NativeIntType) }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stind_R8, Wrap(transition, "StoreIndirect"));
				return this;
			}

			throw new InvalidOperationException("StoreIndirect cannot be used with " + type + ", StoreObject may be more appropriate");
		}
	}
}